package controlador;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import vista.FacturaPanel;
import vista.ListaMaterialesPanel;
import vista.ListaPrendasPanel;
import vista.MaterialPanel;
import vista.PrendaPanel;
import modelo.Factura;
import modelo.Material;
import modelo.OrdenCompra;
import modelo.Prenda;
import modelo.PrendaSinTemporada;
import modelo.PrendaTemporada;
import modelo.Proveedor;


public class Sistema {
	private List<Prenda> prendas;
	private List<Proveedor> proveedores;
	private List<Material> materiales;
	private List<Factura> facturas;
	private List<OrdenCompra> ordenesDeCompra;
	
	private static Sistema instance = null;
	
	
	private Sistema(){
		prendas = new ArrayList<Prenda>();
		proveedores = new ArrayList<Proveedor>();
		materiales = new ArrayList<Material>();
		facturas = new ArrayList<Factura>();
		ordenesDeCompra = new ArrayList<OrdenCompra>();
	}	
	
	public static Sistema getInstance() {
		if(instance == null){
			instance = new Sistema(); 
		}
		
		return instance;
	}

	/**
	 * Alta de un nuevo proveedor en el sistema. El codigo es unico, por lo tanto no debe existir ya un proveedor con el mismo codigo.
	 * @param codigo codigo que representa univocamente al proveedor
	 * @param nombre nombre del proveedor
	 * @return <b>true</b> en caso de que el alta haya sido efectiva, <b>false</b> si ya existia un proveedor con el mismo codigo
	 */
	public boolean altaProveedor(String codigo, String nombre){
		if(this.buscarProveedor(codigo) == null){
			proveedores.add(new Proveedor(codigo, nombre));
			return true;
		}
		return false;
	}
	
	/**
	 * Alta de una PrendaTemporada en el sistema. El codigo es unico, por lo tanto no debe existir ya una prenda con el mismo codigo.
	 * @param codigo codigo que representa univocamente a la prenda
	 * @param nombre nombre de la prenda
	 * @param materiales como clave se utiliza codigo del material y el valor almacenado es la cantidad de material utilizado en la prenda 
	 * @param stock stock de la prenda
	 * @param temporada temporada de la prenda
	 * @return <b>true</b> en caso de que el alta haya sido efectiva, <b>false</b> si ya existia una prenda con el mismo codigo
	 */
	public boolean altaPrendaTemporada(String codigo, String nombre, Map<String, Integer> materialesCC, int stock, String temporada) {
		if(this.buscarPrenda(codigo) == null){
			Map<Material,Integer> materiales = buscarMateriales(materialesCC);
			if(materiales != null) {
				prendas.add(new PrendaTemporada(codigo, nombre, materiales, stock, temporada));
				return true;
			}
		} 
		return false;
	}
	
	private Map<Material,Integer> buscarMateriales(Map<String, Integer> materiales) {
		Map<Material, Integer> result = new HashMap<Material, Integer>();
		Iterator<String> it = materiales.keySet().iterator();		
		while(it.hasNext()) {			
			String codMaterial = it.next();
			Material material = buscarMaterial(codMaterial);
			if(material != null) {
				result.put(material, materiales.get(codMaterial));
			} else {
				return null;
			}
		}
		
		return result;
	}

	/**
	 * Alta de una PrendaSinTemporada en el sistema. El codigo es unico, por lo tanto no debe existir ya una prenda con el mismo codigo.
	 * @param codigo codigo que representa univocamente a la prenda
	 * @param nombre nombre de la prenda
	 * @param materiales como clave se utiliza codigo del material y el valor almacenado es la cantidad de material utilizado en la prenda 
	 * @param stock stock de la prenda
	 * @return <b>true</b> en caso de que el alta haya sido efectiva, <b>false</b> si ya existia una prenda con el mismo codigo
	 */
	public boolean altaPrendaSinTemporada(String codigo, String nombre, Map<String, Integer> materialesCC, int stock) {
		if(this.buscarPrenda(codigo) == null){
			Map<Material, Integer> materiales = buscarMateriales(materialesCC);
			if(materiales != null) {
				prendas.add(new PrendaSinTemporada(codigo, nombre, materiales, stock));
				return true;
			}
			
		}
		
		return false;	
	}
	
	/**
	 * Baja de la prenda con el codigo recibido.
	 * @param codigo codigo de la prenda a dar de baja
	 * @return <b>true</b> en caso de que la baja haya sido efectiva, <b>false</b> si no encontro una prenda con el codigo recibido
	 */
	public boolean bajaPrenda(String codigo) {
		Prenda prendaBaja = buscarPrenda(codigo);
		if (prendaBaja != null){
			prendas.remove(prendaBaja);
			return true;
		}
		return false;
		
	}
	
	/**
	 * Modificaciones de los atributos de una PrendaTemporada y el listado de materiales que la componen. Debe existir una prenda con el codigo recibido.
	 * @param codigo codigo que representa univocamente a la prenda
	 * @param nombre nombre de la prenda
	 * @param materiales listado de materiales que componen a la prenda
	 * @param stock stock de la prenda
	 * @param temporada temporada de la prenda
	 */
	public boolean modificacionPrendaTemporada(String codigo, String nuevoCodigo, String nombre, Map<String, Integer> materialesCC, int stock, String temporada) {
		PrendaTemporada modificada = (PrendaTemporada)buscarPrenda(codigo);
		if(modificada != null) {
			Map<Material, Integer> materiales = buscarMateriales(materialesCC);
			if(materiales != null) {
				modificada.setCodigo(nuevoCodigo);
				modificada.setNombre(nombre);
				modificada.setStock(stock);
				modificada.agregarMateriales(materiales);
				modificada.setTemporada(temporada);
				return true;
			}
		}
		return false;
	}

	/**
	 * Modificaciones de los atributos de una PrendaSinTemporada y el listado de materiales que la componen. Debe existir una prenda con el codigo recibido.
	 * @param codigo codigo que representa univocamente a la prenda
	 * @param nombre nombre de la prenda
	 * @param materiales listado de materiales que componen a la prenda
	 * @param stock stock de la prenda
	 */
	public boolean modificacionPrendaSinTemporada(String codigo, String nuevoCodigo, String nombre, Map<String, Integer> materialesCC, int stock) {
		PrendaSinTemporada modificada = (PrendaSinTemporada)buscarPrenda(codigo);
		if(modificada != null) {
			Map<Material, Integer> materiales = buscarMateriales(materialesCC);
			if(materiales != null) {
				modificada.setCodigo(nuevoCodigo);
				modificada.setNombre(nombre);
				modificada.setStock(stock);
				modificada.agregarMateriales(materiales);
				return true;
			}
		}
		return false;		
	}
	
	public List<Prenda> getPrendas() {
		return prendas;
	}

	/**
	 * Busqueda de una prenda con el codigo recibido.
	 * @param codigo codigo de la prenda a buscar
	 * @return <b>null</b> en caso de que no exista una prenda con el codigo recibido, la Prenda correspondiente al codigo buscado
	 */
	public Prenda buscarPrenda(String codigo){
		Prenda result = null;
		
		for(Prenda prenda: prendas) {
			if(prenda.hasCodigo(codigo)) {
				result = prenda;
				break;
			}
		}
		
		return result;
	}
	
	public List<Proveedor> getProveedores() {
		return proveedores;
	}
	
	/**
	 * Busqueda de un proveedor con el codigo recibido.
	 * @param codigo codigo del proveedor a buscar
	 * @return <b>null</b> en caso de que no exista un proveedor con el codigo recibido, el Proveedor correspondiente al codigo buscado
	 */
	public Proveedor buscarProveedor(String codigo){
		Proveedor result = null;
		
		for(Proveedor proveedor: proveedores) {
			if(proveedor.hasCodigo(codigo)){
				result = proveedor;
				break;
			}
		}
		return result;
	}
		
	/**
	 * Alta de un nuevo material. El codigo es unico, por lo tanto no debe existir ya un material con el mismo codigo. De no existir el proveedor con el codigo
	 * recibido, no se efectua el alta del material.
	 * @param codigo codigo unico del material a agregar
	 * @param nombre nombre del material
	 * @param stock stock del material
	 * @param ptoPedido punto de pedido
	 * @param proveedor codigo del proveedor que provee dicho material
	 * @param precio precio del material
	 * @return <b>true</b> si se agrega el material correctamente, <b>false</b> si no se pudo agregar el material
	 */
	public boolean agregarMaterial(String codigo, String nombre, int stock, int ptoPedido, String codigoProveedor, float precio){
		if (this.buscarMaterial(codigo) == null) {
			Proveedor proveedor = buscarProveedor(codigoProveedor);
			if (proveedor != null) {
				Material nuevoMaterial = new Material(codigo, nombre, stock, ptoPedido, proveedor, precio); 
				materiales.add(nuevoMaterial);
				proveedor.agregarMaterial(nuevoMaterial);
				return true;
			}
		}
		return false;
	}
	
	public boolean modificarMaterial(String codigo, String nuevoCodigo, String nombre, int stock, int ptoPedido, String codigoProveedor, float costo) {
		boolean result = false;
		Material modificado = buscarMaterial(codigo);
		if(modificado != null) {
			Proveedor proveedor = buscarProveedor(codigoProveedor);
			if (proveedor != null) {
				modificado.setCodigo(nuevoCodigo);
				modificado.setNombre(nombre);
				modificado.setStock(stock);
				modificado.setPtoPedido(ptoPedido);
				modificado.setProveedor(proveedor);
				modificado.setPrecio(costo);
				result = true;
			}
		}
		return result;
	}
	
	public boolean bajaMaterial(String codigo) {
		boolean result = false;
		Material material = buscarMaterial(codigo);
		if(material != null) {
				materiales.remove(material);
		}
		return result;
	}
	
	/**
	 * Busqueda del material con el codigo recibido.
	 * @param codigo codigo del material a buscar
	 * @return <b>null</b> en caso de que no exista un material con el codigo recibido, el Material correspondiente al codigo buscado
	 */
	public Material buscarMaterial(String codigo){
		Material result = null;
		
		for(Material material: materiales) {
			if(material.hasCodigo(codigo)) {
				result = material;
				break;
			}
		}
		
		return result;
	}
	
	public List<Material> getMateriales() {
		return materiales;
	}
	
	/**
	 * Se generan ordenes de compra para los materiales cuyo stock este por debajo del punto de pedido. 
	 * @return <b>true</b> si se genero al menos una orden de compra, <b>false</b> si no habia materiales debajo del punto de pedido
	 */
	public boolean generarOrdenesDeCompra() {
		boolean generaOrdenes = false;
		
		for(Proveedor proveedor: proveedores) {
			boolean provAComprar = false;
			OrdenCompra ordenCompra = new OrdenCompra(proveedor);
			for(Material material: proveedor.getMateriales()) {
				if(material.necesitaReponer()) {
					provAComprar = true;
					ordenCompra.agregarItem(material, 10);
					material.aumentarStock(10);
				}
			}
			
			if(provAComprar) {
				ordenesDeCompra.add(ordenCompra);
				generaOrdenes = true;
			}
		}
		
		return generaOrdenes;
	}
	
	public List<OrdenCompra> getOrdenesDeCompra() {
		return ordenesDeCompra;
	}
	
	/**
	 * Generacion de una factura para registrar la venta en un local.
	 * @param prendas como clave se utiliza codigo del material y el valor almacenado es la cantidad vendida
	 */
	public boolean altaFactura(Map<String, Integer> prendasCC) {
		boolean result = false;
		Map<Prenda,Integer> prendas = buscarPrendas(prendasCC);
		if(prendas != null) {
			Factura factura = new Factura(prendas);
			facturas.add(factura);
			result = true;
		}
		
		return result;
	}
	
	public Map<Prenda,Integer> buscarPrendas(Map<String, Integer> prendas) {
		Map<Prenda, Integer> result = new HashMap<Prenda, Integer>();
		Iterator<String> it = prendas.keySet().iterator();		
		while(it.hasNext()) {			
			String codPrenda = it.next();
			Prenda prenda = buscarPrenda(codPrenda);
			if(prenda != null) {
				result.put(prenda, prendas.get(codPrenda));
			} else {
				return null;
			}
		}
		
		return result;
	}
	
	public List<Factura> getFacturas() {
		return facturas;
	}

	public void altaPrendaPanel() {
		PrendaPanel prendaPanel = new PrendaPanel(materiales);
		prendaPanel.showPanel();
	}

	public void altaMaterialPanel() {
		Vector<Proveedor> proveedores = new Vector<Proveedor>(getProveedores());
		MaterialPanel materialPanel = new MaterialPanel(proveedores);
		materialPanel.showPanel();
		
	}
	
	public void mostrarPrendas() {
		ListaPrendasPanel productosPanel = new ListaPrendasPanel(getPrendas(), "Prendas");
		productosPanel.showPanel();
	}
	
	public boolean mostrarPrenda(String codigo) {
		boolean result = false;
		Prenda prenda = buscarPrenda(codigo);
		if (prenda != null) {
			PrendaPanel prendaPanel = new PrendaPanel(getMateriales(), prenda);
			prendaPanel.showPanel();
			result = true;
		}
		return result;
	}
	
	public void mostrarMateriales() {
		ListaMaterialesPanel materialesPanel = new ListaMaterialesPanel(getMateriales());
		materialesPanel.showPanel();
	}
	
	public boolean mostrarMaterial(String codigo) {
		boolean result = false;
		Material material = buscarMaterial(codigo);
		
		if(material != null) {
			result = true;
			Vector<Proveedor> proveedores = new Vector<Proveedor>(getProveedores());
			MaterialPanel materialPanel = new MaterialPanel(proveedores, material);
			materialPanel.showPanel();
		}
		
		return result;
	}
	
	public void altaFacturaPanel() {
		FacturaPanel facturaPanel = new FacturaPanel(getPrendas());
		facturaPanel.showPanel();
	}

	
	public void init() {
		Proveedor p = new Proveedor("PROV1", "Carlos Perez");
		proveedores.add(p);
		Proveedor p2 = new Proveedor("PROV2", "Marcelo Gallardo");
		proveedores.add(p2);
		
		Material m = new Material("M0001", "Algodon",20, 10, p, 5);
		p.agregarMaterial(m);
		materiales.add(m);
		
		m = new Material("M0002", "Velcro", 15, 7, p, 20);
		p.agregarMaterial(m);
		materiales.add(m);
		
		m = new Material("M0003", "Jean", 10, 15, p2, 18);
		p2.agregarMaterial(m);
		materiales.add(m);
		
		m = new Material("M0004", "Cierre", 7, 10, p2, 30);
		p2.agregarMaterial(m);
		materiales.add(m);
		
		m = new Material("M0005", "Nylon", 30, 50, p2, 25);
		p2.agregarMaterial(m);
		materiales.add(m);
		
	}
}
		
